package com.example.demo.util;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.io.FileUtils;
import sun.font.FontDesignMetrics;

import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

/**
 * @Classname QRCodeUtil
 * @Description 图片中央生成二维码
 * @Date 2019/8/13 15:06
 * @Created by yby
 */
public class QRCodeUtil {


    public QRCodeUtil() {}

    private static final int BLACK = 0xFF000000;
    private static final int WHITE = 0xFFFFFFFF;

    /**
     * <pre>
     * 生成二维码图片对象
     * </pre>
     * @author yby
     * @date 2018年11月16日 下午12:44:27
     * @param qrUrl 二维码链接
     * @param qrLen 二维码边长，正方形
     * @return
     * @throws WriterException
     */
    public static BufferedImage getQRImg(String qrUrl, int qrLen) throws WriterException {
        Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
        hints.put(EncodeHintType.MARGIN, 2);
        BitMatrix matrix = new MultiFormatWriter().encode(qrUrl, BarcodeFormat.QR_CODE, qrLen, qrLen, hints);
        BufferedImage image = new BufferedImage(qrLen, qrLen, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < qrLen; x++) {
            for (int y = 0; y < qrLen; y++) {
                image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
            }
        }
        return image;
    }

    /**
     * <pre>
     * 获取字体宽度
     * </pre>
     * @author yby
     * @date 2018年11月16日 下午12:44:36
     * @param font 字体
     * @param text 字符内容
     * @return
     */
    public static int getFontWidth(Font font, String text) {
        FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
        int width = 0;
        for (int i = 0; i < text.length(); i++) {
            width += metrics.charWidth(text.charAt(i));
        }
        return width;
    }

    /**
     * <pre>
     * 生成文字图片对象
     * </pre>
     * @author yby
     * @date 2018年11月16日 下午12:44:39
     * @param text 文字内容
     * @param textFont 字体
     * @param width 图片宽度，小于0将使用字体宽度
     * @param height 图片高度，小于0将使用字体高度
     * @param bgColor 图片背景色
     * @param fontColor 字体色
     * @param fontX 字体x坐标
     * @param fontY 字体y坐标
     * @return
     * @throws Exception
     */
    public static BufferedImage getTextImg(String text, Font textFont, Integer width, Integer height, Color bgColor, Color fontColor, Integer fontX, Integer fontY) throws Exception {
        FontDesignMetrics metrics = FontDesignMetrics.getMetrics(textFont);
        if (width < 0) {
            width = getFontWidth(textFont, text);// 计算图片的宽
        }
        if (height < 0) {
            height = metrics.getHeight();// 计算图片的高
        }
        // 创建一个BufferedImage对象
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = image.createGraphics();
        graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
        graphics.setColor(bgColor); // 先用背景色填充整张图片,也就是背景
        graphics.fillRect(0, 0, width, height);// 画出矩形区域，以便于在矩形区域内写入文字
        graphics.setColor(fontColor);// 再换成字体色，以便于写入文字
        graphics.setFont(textFont);// 设置画笔字体
        graphics.drawString(text, fontX, fontY + metrics.getAscent());// 画出一行字符串
        graphics.dispose();
        return image;
    }

    /**
     * <pre>
     * 将两张图片叠加成一张新的图片
     * 一般情况上层图片小，下层图片大
     * </pre>
     * @author yby
     * @date 2018年11月16日 下午12:44:43
     * @param image 下层图片
     * @param img 上层图片
     * @param x 上层图片叠加位置x坐标，小于0将放于中间位置
     * @param y 上层图片叠加位置y坐标，小于0将放于中间位置
     * @return
     * @throws Exception
     */
    public static BufferedImage imgOnImage(BufferedImage image, BufferedImage img, Integer x, Integer y) throws Exception {
        // 内图的宽高
        int innerWidth = img.getWidth();
        int innerHeigh = img.getHeight();
        if (y < 0) {
            y = (image.getHeight() - innerHeigh) / 2;
        }
        if (x < 0) {
            x = (image.getWidth() - innerWidth) / 2;
        }
        // 开始绘制图片前两个
        Graphics2D g2 = image.createGraphics();
        g2.drawImage(img, x, y, innerWidth, innerHeigh, null);
        //g2.setColor(Color.BLACK);
        g2.setFont(new Font("宋体", Font.BOLD, 30)); // 字体、字型、字号
        g2.dispose(); // 执行刷出返回合成的图片
        image.flush();
        return image;
    }

    /**
     * <pre>
     * 图片对象写入文件
     * </pre>
     * @author yby
     * @date 2018年11月16日 下午12:44:46
     * @param image 图片对象
     * @param imgFile 图片文件
     * @throws IOException
     * @throws WriterException
     */
    public static void imgToFile(BufferedImage image, File imgFile) throws IOException, WriterException {
        // 文件类型
        final String formatName = imgFile.getName().substring(imgFile.getName().lastIndexOf(".") + 1);
        ImageIO.write(image, formatName, imgFile);
    }

    /**
     * <pre>
     * 图片对象写入文件，可设置分辩率
     * </pre>
     * @author fq
     * @date 2018年11月18日 下午1:36:38
     * @param image 图片对象
     * @param imgFile 图片文件
     * @param dpi 分辩率dpi值
     * @throws IOException
     */
    public static void imgToFile(BufferedImage image, File imgFile, Integer dpi) throws IOException {
        imgFile.delete();
        // 文件类型
        final String formatName = imgFile.getName().substring(imgFile.getName().lastIndexOf(".") + 1);
        // 粗略计算发现，设置新生成图片的dpi值在这里要除以25.4，暂不知道是为什么，不明白分辩率是如何计算的
        final String dotsPerMilli = new DecimalFormat("#.00").format(dpi / 25.4);
        for (Iterator<ImageWriter> iw = ImageIO.getImageWritersByFormatName(formatName); iw.hasNext();) {
            ImageWriter writer = iw.next();
            ImageWriteParam writeParam = writer.getDefaultWriteParam();
            ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
            IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);
            if (metadata.isReadOnly() || !metadata.isStandardMetadataFormatSupported()) {
                continue;
            }

            /******************设置分辩率begin************************/
            // 水平分辩率
            IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize");
            horiz.setAttribute("value", dotsPerMilli);
            // 垂直分辩率
            IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize");
            vert.setAttribute("value", dotsPerMilli);
            IIOMetadataNode dim = new IIOMetadataNode("Dimension");
            dim.appendChild(horiz);
            dim.appendChild(vert);
            IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
            root.appendChild(dim);
            metadata.mergeTree("javax_imageio_1.0", root);
            /******************设置分辩率end************************/
            // 数据写入文件
            final ImageOutputStream stream = ImageIO.createImageOutputStream(imgFile);
            try {
                writer.setOutput(stream);
                writer.write(metadata, new IIOImage(image, null, metadata), writeParam);
            } finally {
                stream.close();
            }
            break;
        }
    }

    /**
     * <pre>
     * 图片对象写入输出流
     * </pre>
     * @author fq
     * @date 2018年11月16日 下午12:44:50
     * @param image 图片对象
     * @param imgType 图片类型 PNG JPG等
     * @param stream 输出流
     * @throws IOException
     * @throws WriterException
     */
    public static void imgToStream(BufferedImage image, String imgType, OutputStream stream) throws IOException, WriterException {
        ImageIO.write(image, imgType, stream);
    }

    /**
     * <pre>
     * 获取图片指定坐标处背景色
     * </pre>
     * @author fq
     * @date 2018年11月16日 下午12:44:54
     * @param image 图片对象
     * @param x x坐标
     * @param y y坐标
     * @return
     */
    public static Color getImgRGBColor(BufferedImage image, int x, int y) {
        int green = 248, red = 248, blue = 255;
        Object data = image.getRaster().getDataElements(x, y, null);// 获取像素点
        // ColorModel是一个用来将图片某点的rgb值分别取出的类，包括取出alpha值
        red = image.getColorModel().getRed(data);
        green = image.getColorModel().getGreen(data);
        blue = image.getColorModel().getBlue(data);
        return new Color(248, 248, 255); //此处修改下面编号模板数据变化
    }

    /**
     * <pre>
     * 文件压缩zip
     * </pre>
     * @author fq
     * @date 2018年11月16日 下午2:12:18
     * @param files
     * @param zipFile
     */
    public static void zipCompress(List<File> files, File zipFile) {
        ZipArchiveOutputStream zos = null;
        FileOutputStream zipFos = null;
        ArchiveOutputStream archOut = null;
        try {
            zipFos = new FileOutputStream(zipFile);
            archOut = new ArchiveStreamFactory().createArchiveOutputStream(ArchiveStreamFactory.ZIP, zipFos);
            if (archOut instanceof ZipArchiveOutputStream) {
                zos = (ZipArchiveOutputStream) archOut;
                for (int i = 0; i < files.size(); i++) {
                    zos.putArchiveEntry(new ZipArchiveEntry(files.get(i), files.get(i).getName()));
                    zos.write(FileUtils.readFileToByteArray(files.get(i)));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != zos) {
                    zos.flush();
                    zos.closeArchiveEntry();
                    zos.close();
                }
                if (null != archOut) {
                    archOut.close();
                }
                if (null != zipFos) {
                    zipFos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * <pre>
     * Image转BufferedImage
     * </pre>
     * @author fq
     * @date 2018年11月18日 下午1:37:41
     * @param image
     * @param type
     * @return
     */
    public static BufferedImage toBufferedImage(Image image, int type) {
        int w = image.getWidth(null);
        int h = image.getHeight(null);
        BufferedImage result = new BufferedImage(w, h, type);
        Graphics2D g = result.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return result;
    }



    /**
     * <pre>
     * 测试生成二维码方法
     * </pre>
     * @author fq
     * @date 2018年11月19日 下午5:01:25
     * @param code
     * @return
     * @throws Exception
     */
    public static File testQr(String code,String srcImg,String newSrcImg,String url) throws Exception {
        // 获取模板图片对象
        BufferedImage image = ImageIO.read(new File(srcImg));
        // 生成二维码图片对象  改变二维码大小
        BufferedImage qrImg = getQRImg(url, 360);
        // 获取模板一个坐标点的背景色对象
        Color bgColor = getImgRGBColor(image, 10, 10);
        // 生成编号号图片对象
        BufferedImage codeImg = getTextImg(code, new Font("Arial Bold", Font.BOLD, 20), -1, -1, bgColor, Color.BLACK, 0, 0);
        // 将二维码图片合成到模板图片上  改变位置
        image = imgOnImage(image, qrImg, 300, 300);
        // 将编号号图片合成到模板图片上  改变位置
        image = imgOnImage(image, codeImg, 300, 635);
        // 创建新的图片文件
        File newImg = new File(newSrcImg);
        if (!newImg.getParentFile().exists()) {
            newImg.getParentFile().mkdirs();
        }
        if (!newImg.exists()) {
            newImg.createNewFile();
        }
        // 将合成的图片写入图片文件，保存本地文件
        imgToFile(image, newImg, 150);
        return newImg;
    }

    public static void main(String[] args) throws Exception {
        testQr("1A2586","D:\\Pictures\\374cfff59e.jpg","D:\\\\qr\\\\testQr\\111.png","http://xs.xinsen.ltd/lottery/default.html?cpOdd=mqttTest1");
        // 编号号
        String[] codes = {"测试图片", "02", "03", "04"};
        // 生成的所有图片
        List<File> qrFiles = new ArrayList<>();
        for (int i = 0; i < codes.length; i++) {
            qrFiles.add(testQr("1A2586","D:\\Pictures\\374cfff59e.jpg","D:\\\\qr\\\\testQr\\111"+i+".png","http://xs.xinsen.ltd/lottery/default.html?cpOdd=mqttTest1"));
        }
        if (!qrFiles.isEmpty()) {
            // 图片文件压缩包
            File zipFile = new File("D:\\qr\\testCodes.zip");
            if (!zipFile.getParentFile().exists()) {
                zipFile.getParentFile().mkdirs();
            }
            if (!zipFile.exists()) {
                zipFile.createNewFile();
            }
            // 压缩图片文件
            zipCompress(qrFiles, zipFile);
            // 删除生成的所有图片文件夹
            /*File dir = qrFiles.get(0).getParentFile();
            if (dir.exists() && dir.isDirectory()) {
                FileUtils.deleteDirectory(dir);
            }*/
        }
    }



}
